#include <QtCore/qmath.h>
#include <QtCore/QtEndian>
#include <QtCore/QDateTime>
#include <QtSql/QSqlRecord>
#include "kernel/kernel.h"
#include "kernel/kernel_p.h"

namespace smsbilling {

static Kernel *kernel_instance = 0;

KernelPrivate::KernelPrivate() :
  QObject(0),
  log_level(smslog::WARNING),
  db_port(-1),
  num_connect_db(-1),
  sync_db_timer(-1),
  billing_server(0),
  db_manager(0),
  thread(new QThread(this)),
  state(STOPED),
  settings(new KernelSettings(0, "", 0, "", 5000, NO_UPDATED)) {
  moveToThread(thread);

  connect(thread, SIGNAL(started()), this, SLOT(threadStarted()));

  connect(this, SIGNAL(sigStop()), this, SLOT(stop()));
}

KernelPrivate::~KernelPrivate() {
  if(billing_server) {
    delete billing_server;
    billing_server = 0;
  }
  if(db_manager) {
    delete db_manager;
    db_manager = 0;
  }
  if(settings) {
    delete settings;
    settings = 0;
  }
  kernel_instance = 0;
}

void KernelPrivate::timerEvent(QTimerEvent *e) {
  if(e->timerId() == sync_db_timer) {
    syncDatabase();
  }
}

void KernelPrivate::threadStarted() {
  billing_server = new binaryrpc::Server;

  connect(billing_server, SIGNAL(newSession(binaryrpc::Session *)),
          this, SLOT(newBillingSession(binaryrpc::Session *)));

  db_manager = new dbmanager::DatabaseManager(db_host,
                                              db_port,
                                              db_name,
                                              db_uname,
                                              db_pass,
                                              num_connect_db,
                                              "smsbilling");

  setState(STARTING);
}

void KernelPrivate::stop() {
  setState(STOPING);

  checkState();
}

void KernelPrivate::initServerExecuted() {
  dbmanager::SqlQuery *q = qobject_cast<dbmanager::SqlQuery *>(sender());

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    QList<QSqlRecord *> res(q->results());
    if(res.isEmpty()) {
      if(log_level >= smslog::ERROR) {
        LogEncoder::instance()->encode(smslog::ERROR,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_INIT_SERVER_ERR);
      }

      setState(STOPING);
    } else if(state == STARTING) {
      QSqlRecord *r(res.takeFirst());

      settings->rpc_ip = r->value(0).toString();
      settings->rpc_port = r->value(1).toUInt();
      settings->name = r->value(2).toString();
      settings->sync_db_time = r->value(3).toUInt();
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initServer"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::initTariffsExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    if(state == STARTING) {
      quint32 tariff_id(0);
      QString tariff_name;
      QList<TariffAddr *> addresses;
      QList<QSqlRecord *> res(q->results());
      while(!res.isEmpty()) {
        QSqlRecord *r(res.takeFirst());

        quint32 now_tariff_id(r->value(0).toUInt());

        if(tariff_id == 0) {
          tariff_id = now_tariff_id;
          tariff_name = r->value(1).toString();
        }

        if(now_tariff_id != tariff_id && !addresses.isEmpty()) {
          Tariff *t(new Tariff(tariff_id,
                               tariff_name,
                               addresses,
                               NO_UPDATED));

          tariffs.push_back(t);

          tariff_id = now_tariff_id;
          tariff_name = r->value(1).toString();
          addresses.clear();
        }

        addresses.push_back(new TariffAddr(r->value(2).toUInt(),
                                           roundUp(r->value(5).toFloat(), 2),
                                           QRegExp(r->value(3).toString()),
                                           QRegExp(r->value(4).toString()),
                                           NO_UPDATED));
      }

      if(addresses.isEmpty() == false) {
        Tariff *t(new Tariff(tariff_id,
                             tariff_name,
                             addresses,
                             NO_UPDATED));

        tariffs.push_back(t);
      }
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initTariffs"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::initResourcesExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success()) {
    if(state == STARTING) {
      QList<QSqlRecord *> res(q->results());
      while(!res.isEmpty()) {
        QSqlRecord *r(res.takeFirst());

        Resource *res(new Resource(r->value(0).toUInt(),
                                   r->value(1).toUInt(),
                                   r->value(2).toString(),
                                   roundUp(r->value(3).toFloat(), 2),
                                   r->value(5).toUInt(),
                                   r->value(6).toULongLong(),
                                   r->value(4).toBool(),
                                   NO_UPDATED));

        resources.push_back(res);
      }
    }
  } else {
    if(log_level >= smslog::WARNING) {
      QHash<int, QVariant> params;
      params.insert(logparam::ACTION_P, QString("initResources"));
      params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
      params.insert(logparam::ERROR_MSG_P, q->getFaultString());

      LogEncoder::instance()->encode(smslog::WARNING,
                                     logclass::KERNEL,
                                     logclass::KERNEL,
                                     logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                     params);
    }

    setState(STOPING);
  }

  delete q;

  checkState();
}

void KernelPrivate::syncDatabaseExecuted() {
  dbmanager::SqlQuery *q(qobject_cast<dbmanager::SqlQuery *>(sender()));

  if(!q) {
    return;
  }

  queries.removeAll(q);

  if(q->success() == false && log_level >= smslog::WARNING) {
    QHash<int, QVariant> params;
    params.insert(logparam::ACTION_P, QString("syncDatabase"));
    params.insert(logparam::ERROR_CODE_P, q->getFaultCode());
    params.insert(logparam::ERROR_MSG_P, q->getFaultString());

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::ERROR_EXEC_SQL_QUERY,
                                   params);
  }

  delete q;

  checkState();
}

void KernelPrivate::newBillingSession(binaryrpc::Session *session) {
  connect(session, SIGNAL(onOpen()), this, SLOT(openSession()));
  connect(session, SIGNAL(onClose()), this, SLOT(closeSession()));

  if(state == STARTED) {
    connect(session, SIGNAL(onError(const QString &)),
            this, SLOT(errorSession(const QString &)));
    connect(session, SIGNAL(newMessage(binaryrpc::ServerMessage *)),
            this, SLOT(newMessageSession(binaryrpc::ServerMessage *)));
  } else {
    session->close();
  }
}

void KernelPrivate::openSession() {
  binaryrpc::Session *session = qobject_cast<binaryrpc::Session *>(sender());

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_OPEN_SESSION,
                                   params);
  }

  sessions.push_back(session);
}

void KernelPrivate::closeSession() {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::INFO) {
    QHash<int, QVariant> params;
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::INFO,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_CLOSE_SESSION,
                                   params);
  }

  sessions.removeAll(session);

  delete session;
}

void KernelPrivate::errorSession(const QString &error_string) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  if(log_level >= smslog::WARNING) {
    QHash<int, QVariant> params;
    params.insert(logparam::ERROR_MSG_P, error_string);
    params.insert(logparam::PEER_IP_P, session->peerAddress().toString());
    params.insert(logparam::PEER_PORT_P, session->peerPort());
    params.insert(logparam::LOCAL_IP_P, session->localAddress().toString());
    params.insert(logparam::LOCAL_PORT_P, session->localPort());

    LogEncoder::instance()->encode(smslog::WARNING,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::K_ERROR_SESSION,
                                   params);
  }

  sessions.removeAll(session);

  delete session;
}

void KernelPrivate::newMessageSession(binaryrpc::ServerMessage *message) {
  binaryrpc::Session *session(qobject_cast<binaryrpc::Session *>(sender()));

  if(!session) {
    return;
  }

  QString method_name(message->name());
  QVariantList parameters(message->parameters());

  if(method_name == "changeResource") {
    bool success(parameters.size() == 3);

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    quint32 resource_id(parameters[0].toUInt(&success));

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    QString address(parameters[1].toString());

    int resource_num(parameters[2].toInt(&success));

    if(!success) {
      message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
      message->deleteLater();
      return;
    }

    Resource *res = getResource(resource_id);

    if(!res) {
      message->done(QVariant(false));
      message->deleteLater();
      return;
    }

    QDateTime now_date(QDateTime::currentDateTime());

    if(res->counter_null <= (quint64)now_date.toMSecsSinceEpoch()) {
      quint64 start_date(0),
              stop_date(res->counter_null);

      if(res->counter_null > counter_life_int) {
        start_date = res->counter_null - counter_life_int;
      }

      if(!queries_to_resources_history.isEmpty()) {
        queries_to_resources_history += ",";
      }

      queries_to_resources_history += QString("(%1,%2,%3,%4,%5)")
        .arg(res->id)
        .arg(res->counter)
        .arg(res->balance)
        .arg(start_date)
        .arg(stop_date);

      res->counter_null += counter_life_int;
      res->counter = 0;
      if(res->state == NO_UPDATED) {
        res->state = UPDATED;
      }
    }

    Tariff *tar = getTariff(res->tariff_id);

    if(!tar) {
      message->done(QVariant(false));
      message->deleteLater();
      return;
    }

    if(res->state == ADDED ||
       res->state == NO_UPDATED ||
       res->state == UPDATED) {
      int plus_counter(resource_num * -1);

      float cost(getCostTariff(tar, address, res->counter + plus_counter));

      if(cost == -1) {
        message->done(QVariant(false));
        message->deleteLater();
        return;
      }

      if(plus_counter < 0 && (quint64)(plus_counter * -1) > res->counter) {
        res->counter = 0;
      } else {
        res->counter += plus_counter;
      }

      float debited(roundUp(resource_num * cost, 2));

      float new_balance(res->balance + debited);

      if((new_balance < 0 && res->postpaid) || new_balance >= 0) {
        res->balance = new_balance;

        if(res->state == NO_UPDATED) {
          res->state = UPDATED;
        }

        QString db_postfix(now_date.toString("dd_MM_yyyy"));

        if(queries_to_debited.contains(db_postfix)) {
          queries_to_debited[db_postfix] += ",";
        } else {
          queries_to_debited.insert(db_postfix, QString());
        }

        queries_to_debited[db_postfix] += QString("(%1,%2,%3)")
          .arg(res->id)
          .arg((quint64)now_date.toMSecsSinceEpoch())
          .arg(debited);

        message->done(QVariant(true));
      } else {
        message->done(QVariant(false));
      }
    } else {
      message->done(QVariant(false));
    }
  } else {
    message->fault(BRPC_ERR_CODE_NOT_FOUND, BRPC_ERR_CODE_NOT_FOUND_D);
  }

  message->deleteLater();
}

void KernelPrivate::syncDatabase() {
  dbmanager::SqlQuery *query(0);

  quint64 now_date(QDateTime::currentMSecsSinceEpoch());

  for(int i = 0; i < resources.size(); i++) {
    Resource *r = resources[i];

    if(r->counter_null <= now_date) {
      quint64 start_date(0),
              stop_date(r->counter_null);

      if(r->counter_null > counter_life_int) {
        start_date = r->counter_null - counter_life_int;
      }

      if(!queries_to_resources_history.isEmpty()) {
        queries_to_resources_history += ",";
      }

      queries_to_resources_history += QString("(%1,%2,%3,%4,%5)")
        .arg(r->id)
        .arg(r->counter)
        .arg(r->balance)
        .arg(start_date)
        .arg(stop_date);

      r->counter_null += counter_life_int;
      r->counter = 0;
      if(r->state == NO_UPDATED) {
        r->state = UPDATED;
      }
    }
  }

  if(!queries_to_debited.isEmpty() && query == 0) {
    query = new dbmanager::SqlQuery(false, true);
  }

  for(QHash<QString, QString>::iterator i = queries_to_debited.begin();
      i != queries_to_debited.end();
      i++) {
    query->add(QString("INSERT INTO `debited_%1` ("
                       "`resource_id`,"
                       "`write_off_date`,"
                       "`debited`) VALUES %2")
               .arg(i.key())
               .arg(i.value()));
  }

  queries_to_debited.clear();


  if(!queries_to_resources_history.isEmpty()) {
    if(query == 0) {
      query = new dbmanager::SqlQuery(false, true);
    }
    query->add(QString("INSERT INTO `resources_history` ("
                       "`resource_id`,"
                       "`counter`,"
                       "`balance`,"
                       "`start_date`,"
                       "`end_date`) VALUES ")
                .append(queries_to_resources_history));
  }

  queries_to_resources_history.clear();

  QString insert_tariff;
  QString update_tariff_id;
  QString update_tariff_name;
  QString delete_tariff;

  for(int i = 0; i < tariffs.size(); i++) {
    Tariff *t = tariffs[i];

    switch(t->state) {
      case NO_UPDATED:
        break;
      case UPDATED:
        if(!update_tariff_id.isEmpty()) {
          update_tariff_id += ",";
        }
        if(!update_tariff_name.isEmpty()) {
          update_tariff_name += " ";
        }

        update_tariff_id += QString::number(t->id);
        update_tariff_name +=
          QString("WHEN %1 THEN '%2'").arg(t->id).arg(t->name);

        t->state = NO_UPDATED;

        break;
      case ADDED:
        if(!insert_tariff.isEmpty()) {
          insert_tariff += ",";
        }
        insert_tariff += QString("(%1,'%2')")
                         .arg(t->id)
                         .arg(t->name);

        t->state = NO_UPDATED;

        break;
      case DELETED:
        if(!delete_tariff.isEmpty()) {
          delete_tariff += ",";
        }
        delete_tariff += QString::number(t->id);

        tariffs.takeAt(i);
        i--;
        delete t;

        break;
    }
  }

  if(!insert_tariff.isEmpty() ||
     !update_tariff_name.isEmpty() ||
     !delete_tariff.isEmpty()) {
    if(query == 0) {
      query = new dbmanager::SqlQuery(false, true);
    }

    if(!insert_tariff.isEmpty()) {
      query->add(QString("INSERT INTO `tariffs` ("
                         "`id`,"
                         "`name`) VALUES ").append(insert_tariff));
    }

    if(!update_tariff_id.isEmpty()) {
      query->add(QString(
                   "UPDATE `tariffs` "
                   "SET `name` = CASE `id` %1 END "
                   "WHERE `server_id`=%2 AND `id` IN (%3)")
                 .arg(update_tariff_name)
                 .arg(settings->id)
                 .arg(update_tariff_id));
    }

    if(!delete_tariff.isEmpty()) {
      query->add(QString("DELETE FROM `tariffs` WHERE `id` IN (%1)")
                   .arg(delete_tariff));
    }
  }

  QString insert_resource;
  QString update_resource_id;
  QString update_resource_tariff_id;
  QString update_resource_name;
  QString update_resource_balance;
  QString update_resource_postpaid;
  QString update_resource_counter;
  QString update_resource_counter_null;
  QString delete_resource;

  for(int i = 0; i < resources.size(); i++) {
    Resource *r = resources[i];

    switch(r->state) {
      case NO_UPDATED:
        break;
      case UPDATED:
        if(!update_resource_id.isEmpty()) {
          update_resource_id += ",";
        }
        if(!update_resource_tariff_id.isEmpty()) {
          update_resource_tariff_id += " ";
        }
        if(!update_resource_name.isEmpty()) {
          update_resource_name += " ";
        }
        if(!update_resource_balance.isEmpty()) {
          update_resource_balance += " ";
        }
        if(!update_resource_postpaid.isEmpty()) {
          update_resource_postpaid += " ";
        }
        if(!update_resource_counter.isEmpty()) {
          update_resource_counter += " ";
        }
        if(!update_resource_counter_null.isEmpty()) {
          update_resource_counter_null += " ";
        }

        update_resource_id += QString::number(r->id);
        update_resource_tariff_id +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->tariff_id);
        update_resource_name +=
          QString("WHEN %1 THEN '%2'").arg(r->id).arg(r->name);
        update_resource_balance +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->balance);
        update_resource_postpaid +=
          QString("WHEN %1 THEN %2").arg(r->id).arg((int)r->postpaid);
        update_resource_counter +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->counter);
        update_resource_counter_null +=
          QString("WHEN %1 THEN %2").arg(r->id).arg(r->counter_null);

        r->state = NO_UPDATED;

        break;
      case ADDED:
        if(!insert_resource.isEmpty()) {
          insert_resource += ",";
        }
        insert_resource += QString("(%1,%2,%3,'%4',%5,%6,%7,%8)")
                             .arg(r->id)
                             .arg(settings->id)
                             .arg(r->tariff_id)
                             .arg(r->name)
                             .arg(r->balance)
                             .arg((int)r->postpaid)
                             .arg(r->counter)
                             .arg(r->counter_null);

        r->state = NO_UPDATED;

        break;
      case DELETED:
        if(!delete_resource.isEmpty()) {
          delete_resource += ",";
        }
        delete_resource += QString::number(r->id);

        resources.takeAt(i);
        i--;
        delete r;

        break;
    }
  }

  if(!insert_resource.isEmpty() ||
     !update_resource_id.isEmpty() ||
     !delete_resource.isEmpty()) {
     if(!query) {
       query = new dbmanager::SqlQuery(false, true);
     }

     if(!insert_resource.isEmpty()) {
       query->add(QString("INSERT INTO `resources` ("
                          "`id`,"
                          "`server_id`,"
                          "`tariff_id`,"
                          "`name`,"
                          "`balance`,"
                          "`postpaid`,"
                          "`counter`,"
                          "`counter_null`) VALUES ").append(insert_resource));
     }

     if(!update_resource_id.isEmpty()) {
       query->add(QString(
                    "UPDATE `resources` "
                    "SET `tariff_id` = CASE `id` %1 END,"
                    "`name` = CASE `id` %2 END,"
                    "`balance` = CASE `id` %3 END,"
                    "`postpaid` = CASE `id` %4 END,"
                    "`counter` = CASE `id` %5 END,"
                    "`counter_null` = CASE `id` %6 END "
                    "WHERE `server_id`=%7 AND `id` IN (%8)")
                  .arg(update_resource_tariff_id)
                  .arg(update_resource_name)
                  .arg(update_resource_balance)
                  .arg(update_resource_postpaid)
                  .arg(update_resource_counter)
                  .arg(update_resource_counter_null)
                  .arg(settings->id)
                  .arg(update_resource_id));
     }

     if(!delete_resource.isEmpty()) {
       query->add(QString("DELETE FROM `resources` WHERE `id` IN (%1)")
                    .arg(delete_resource));
     }
  }

  QString insert_ta;
  QString update_ta_id;
  QString update_ta_tariff_id;
  QString update_ta_addr;
  QString update_ta_num;
  QString update_ta_cost;
  QString delete_ta;

  for(int i = 0; i < tariffs.size(); i++) {
    Tariff *t = tariffs[i];

    for(int j = 0; j < t->addresses.size(); j++) {
      TariffAddr *ta(t->addresses[j]);
      switch(ta->state) {
        case NO_UPDATED:
          break;
        case UPDATED:
          if(!update_ta_id.isEmpty()) {
            update_ta_id += ",";
          }
          if(!update_ta_tariff_id.isEmpty()) {
            update_ta_tariff_id += " ";
          }
          if(!update_ta_addr.isEmpty()) {
            update_ta_addr += " ";
          }
          if(!update_ta_num.isEmpty()) {
            update_ta_num += " ";
          }
          if(!update_ta_cost.isEmpty()) {
            update_ta_cost += " ";
          }

          update_ta_id += QString::number(ta->id);
          update_ta_tariff_id +=
            QString("WHEN %1 THEN %2").arg(ta->id).arg(t->id);
          update_ta_addr +=
            QString("WHEN %1 THEN %2").arg(ta->id).arg(ta->addr.pattern());
          update_ta_num +=
            QString("WHEN %1 THEN '%2'").arg(ta->id).arg(ta->num.pattern());
          update_ta_cost +=
            QString("WHEN %1 THEN %2").arg(ta->id).arg(ta->cost);

          ta->state = NO_UPDATED;

          break;
        case ADDED:
          if(!insert_ta.isEmpty()) {
            insert_ta += ",";
          }
          insert_ta += QString("(%1,%2,'%3','%4',%5)")
                         .arg(ta->id)
                         .arg(t->id)
                         .arg(ta->addr.pattern())
                         .arg(ta->num.pattern())
                         .arg(ta->cost);

          ta->state = NO_UPDATED;

          break;
        case DELETED:
          if(!delete_ta.isEmpty()) {
            delete_ta += ",";
          }
          delete_ta += QString::number(ta->id);

          t->addresses.takeAt(j);
          j--;
          delete ta;

          break;
      }
    }
  }

  if(!insert_ta.isEmpty() ||
     !update_ta_id.isEmpty() ||
     !delete_ta.isEmpty()) {
     if(!query) {
       query = new dbmanager::SqlQuery(false, true);
     }

     if(!insert_ta.isEmpty()) {
       query->add(QString("INSERT INTO `tariff_addresses` ("
                          "`id`,"
                          "`tariff_id`,"
                          "`addr`,"
                          "`num`,"
                          "`cost`) VALUES ").append(insert_ta));
     }

     if(!update_resource_id.isEmpty()) {
       query->add(QString(
                    "UPDATE `tariff_addresses` "
                    "SET `tariff_id` = CASE `id` %1 END,"
                    "`addr` = CASE `id` %2 END,"
                    "`num` = CASE `id` %3 END,"
                    "`cost` = CASE `id` %4 END "
                    "WHERE `id` IN (%5)")
                  .arg(update_ta_tariff_id)
                  .arg(update_ta_addr)
                  .arg(update_ta_num)
                  .arg(update_ta_cost)
                  .arg(update_ta_id));
     }

     if(!delete_ta.isEmpty()) {
       query->add(QString("DELETE FROM `tariff_addresses` WHERE `id` IN (%1)")
                    .arg(delete_ta));
     }
  }

  if(query) {
    connect(query, SIGNAL(executed()), this, SLOT(syncDatabaseExecuted()));

    queries.push_back(query);

    db_manager->execute(query);
  }
}

bool KernelPrivate::readConfig(QSettings *config) {
  config->beginGroup(KERNEL_KEY);

  bool res(config->contains(LOG_LEVEL_KEY));

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(LOG_LEVEL_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(SERVER_ID_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(SERVER_ID_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_HOST_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_HOST_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PORT_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PORT_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_NAME_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_NAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_UNAME_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_UNAME_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(DB_PASS_KEY);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(DB_PASS_KEY));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  res = config->contains(NUM_CONNECT_DB);

  if(!res) {
    QHash<int, QVariant> params;
    params.insert(logparam::CONF_ROOT_KEY_P, QString(KERNEL_KEY));
    params.insert(logparam::CONF_1_KEY_P, QString(NUM_CONNECT_DB));
    LogEncoder::instance()->encode(smslog::ERROR,
                                   logclass::KERNEL,
                                   logclass::KERNEL,
                                   logmsgcodes::CONF_NOT_FOUND_KEY_1_2,
                                   params);
    config->endGroup();
    return res;
  }

  //TODO: Check transform parameters
  mutex.lock();
  log_level = (smslog::LogLevel)config->value(LOG_LEVEL_KEY).toInt();
  settings->id = config->value(SERVER_ID_KEY).toUInt();
  db_host = config->value(DB_HOST_KEY).toString();
  db_port = config->value(DB_PORT_KEY).toInt();
  db_name = config->value(DB_NAME_KEY).toString();
  db_uname = config->value(DB_UNAME_KEY).toString();
  db_pass = config->value(DB_PASS_KEY).toString();
  num_connect_db = config->value(NUM_CONNECT_DB).toInt();
  mutex.unlock();

  config->endGroup();

  return res;
}

void KernelPrivate::setState(State s) {
  if(state == s)
    return;

  switch(s) {
    case STOPING:
      if(state == STOPED) {
        return;
      } else {
        if(sync_db_timer != -1) {
          killTimer(sync_db_timer);
          sync_db_timer = -1;
        }

        billing_server->close();

        while(!sessions.isEmpty()) {
          binaryrpc::Session *session(sessions.takeFirst());
          session->close();
          session->deleteLater();
        }

        syncDatabase();

        if(log_level >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STOPING_STATE);
        }
      }
      break;
    case STARTING:
      if(state == STARTED) {
        return;
      } else {
        if(log_level >= smslog::INFO) {
          LogEncoder::instance()->encode(smslog::INFO,
                                         logclass::KERNEL,
                                         logclass::KERNEL,
                                         logmsgcodes::K_STARTING_STATE);
        }
        initServer();
        initTariffs();
        initResources();
      }
      break;
    case STOPED:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STOPED_STATE);
      }
      break;
    case STARTED:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_STARTED_STATE);
      }
      break;
    case ERROR:
      if(log_level >= smslog::INFO) {
        LogEncoder::instance()->encode(smslog::INFO,
                                       logclass::KERNEL,
                                       logclass::KERNEL,
                                       logmsgcodes::K_ERROR_STATE);
      }
      break;
  }

  state = s;

  emit Kernel::instance()->stateChanged(state);
}

void KernelPrivate::initServer() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT `rpc_ip`,`rpc_port`,`name`,`sync_db_time` "
                     "FROM `servers` "
                     "WHERE `id`=%1 LIMIT 1")
             .arg(Kernel::instance()->getServerId()));

  connect(query, SIGNAL(executed()), this, SLOT(initServerExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::initTariffs() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add("SELECT t.`id`,t.`name`,ta.`id`,ta.`addr`,ta.`num`,ta.`cost` "
             "FROM `tariffs` t,`tariff_addresses` ta "
             "WHERE ta.`tariff_id`=t.`id` ORDER BY t.`id`");

  connect(query, SIGNAL(executed()), this, SLOT(initTariffsExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::initResources() {
  dbmanager::SqlQuery *query = new dbmanager::SqlQuery;

  query->add(QString("SELECT r.`id`,"
                     "r.`tariff_id`,"
                     "r.`name`,"
                     "r.`balance`,"
                     "r.`postpaid`,"
                     "r.`counter`,"
                     "r.`counter_null` "
                     "FROM `servers` s,`resources` r "
                     "WHERE r.`server_id`=s.`id` AND s.`id`=%1")
             .arg(Kernel::instance()->getServerId()));

  connect(query, SIGNAL(executed()), this, SLOT(initResourcesExecuted()));

  queries.push_back(query);

  db_manager->execute(query);
}

void KernelPrivate::checkState() {
  switch(state) {
    case STOPING:
      if(queries.isEmpty()) {
        setState(STOPED);
      }
      break;
    case STARTING:
      if(queries.isEmpty()) {
        if(billing_server->listen(QHostAddress(settings->rpc_ip),
                                  settings->rpc_port)) {
          if(log_level >= smslog::INFO) {
            QHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, settings->rpc_ip);
            params.insert(logparam::LOCAL_PORT_P, settings->rpc_port);

            LogEncoder::instance()->encode(smslog::INFO,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_LISTEN_SERVER,
                                           params);
          }

          if(settings->sync_db_time >= 5000) {
            sync_db_timer = startTimer(settings->sync_db_time);
          } else {
            sync_db_timer = startTimer(5000);
          }

          setState(STARTED);
        } else {
          if(log_level >= smslog::ERROR) {
            QHash<int, QVariant> params;
            params.insert(logparam::LOCAL_IP_P, settings->rpc_ip);
            params.insert(logparam::LOCAL_PORT_P, settings->rpc_port);
            params.insert(logparam::ERROR_CODE_P,
                          (int)billing_server->serverError());
            params.insert(logparam::ERROR_MSG_P, billing_server->errorString());

            LogEncoder::instance()->encode(smslog::ERROR,
                                           logclass::KERNEL,
                                           logclass::KERNEL,
                                           logmsgcodes::K_NOT_LISTEN_SERVER,
                                           params);
          }

          setState(STOPED);
        }
      }
      break;
    default:
      break;
  }
}

Tariff *KernelPrivate::getTariff(quint32 id) const {
  Tariff *t(0);
  for(int i = 0; i < tariffs.size(); i++) {
    Tariff *tmp_p(tariffs[i]);
    if(tmp_p->id == id) {
      t = tmp_p;
      break;
    }
  }

  return t;
}

Resource *KernelPrivate::getResource(quint32 id) const {
  Resource *r(0);
  for(int i = 0; i < resources.size(); i++) {
    Resource *tmp_p(resources[i]);
    if(tmp_p->id == id) {
      r = tmp_p;
      break;
    }
  }

  return r;
}

float KernelPrivate::getCostTariff(Tariff *tariff,
                                   const QString &address,
                                   quint32 num) const {
  float cost(-1);
  for(int i = 0; i < tariff->addresses.size(); i++) {
    TariffAddr *addr(tariff->addresses[i]);
    if(addr->addr.exactMatch(address) &&
       addr->num.exactMatch(QString::number(num))) {
      cost = addr->cost;
      break;
    }
  }

  return cost;
}

float KernelPrivate::roundUp(float val, quint32 digits) const {
  return qFloor(val * qPow(10.0, digits) + 0.5) / qPow(10.0, digits);
}

Kernel::Kernel() : QObject(0), p(new KernelPrivate) {
  kernel_instance = this;
}

Kernel::~Kernel() {
  if(p) {
    if(p->thread) {
      if(p->thread->isRunning()) {
        p->thread->quit();
        p->thread->wait();
      }
    }
    delete p;
    p = 0;
  }
}

Kernel *Kernel::instance() {
  return kernel_instance;
}

dbmanager::DatabaseManager *Kernel::db() {
  return p->db_manager;
}

KernelSettings *Kernel::settings() const {
  return p->settings;
}

quint32 Kernel::getServerId() const {
  return p->settings->id;
}

smslog::LogLevel Kernel::getLogLevel() const {
  return p->log_level;
}

QString Kernel::getDbHost() const {
  return p->db_host;
}

quint16 Kernel::getDbPort() const {
  return p->db_port;
}

QString Kernel::getDbName() const {
  return p->db_name;
}

QString Kernel::getDbUserName() const {
  return p->db_uname;
}

QString Kernel::getDbPassword() const {
  return p->db_pass;
}

int Kernel::getNumConnectDb() const {
  return p->num_connect_db;
}

bool Kernel::readConfig(QSettings *config) {
  return p->readConfig(config);
}

void Kernel::start() {
  p->thread->start();
}

void Kernel::stop() {
  emit p->sigStop();
}

QString Kernel::getLastError() const {
  return p->last_error;
}

} // namespace smsbilling
